
/*
 * @项目名称: 模板开发
 * @FilePath: /meek/src/geometry/line.js
 * @Author: 
 * @Date: 2017-03-23T11:12:12+08:00
 * @LastEditors: yangxianghong@datatang.com
 * @LastEditTime: 2023-07-12 17:35:21
 * Copyright (c) 2018-2022 
 */
                                                    
/**
 * Created by zypc on 2016/11/13.
 */

import Geometry from './geometry'
import Extent from './extent'

import { squaredSegmentDistance, distance, pointDistanceToSegment, getProjectionPointOnLine } from './support/geometryutil'
import { lineString } from './support/interpolate'
import cloneDeep from 'lodash/cloneDeep';
import { ExtentUtil } from '../geometry/support/extentutil'
/**
 * 定义线类和数据结构
 *
 * @class Line
 * @extends Geometry
 * @module geometry
 * @constructor
 * @example
 *
 *  var line = new Datatang.Line()
 *
 *  line.setCoordinates([100, 100], [200, 200], [400, 500])
 */
export default class Line extends Geometry {

  /**
   * Create a line geometry
   * @constructor
   * @param path
   */
  constructor(path) {
    super()

    /**
     * 记录内点更新的次数
     * @type {number}
     * @private
     */
    this._flatInteriorPointRevision = -1

    /**
     * 缓存当前内点
     * @type {null}
     * @private
     */
    this._flatInteriorPoint = null
    this.lineMode = Line.lineMode
    /**
     *
     * @type {Array}
     * @private
     */
    this._path = []

    this.bezier = []

    this.addPath(path)
  }

  get geometryType() { return Geometry.LINE }

  /**
   * 获取图形的最小外接矩形(MBR-Minimum Bounding Rectangle)
   *
   * @returns {Object} 返回对象的最小外接矩形
   *
   */
  get extent() {
    const me = this
    if (me._extent === null) {
      let xmin = Number.POSITIVE_INFINITY
      let ymin = Number.POSITIVE_INFINITY
      let xmax = Number.NEGATIVE_INFINITY
      let ymax = Number.NEGATIVE_INFINITY

      const coors = me.getCoordinates().concat(me._getAllControls());

      for (let p of coors) {
        xmin = Math.min(xmin, p[0])
        ymin = Math.min(ymin, p[1])
        xmax = Math.max(xmax, p[0])
        ymax = Math.max(ymax, p[1])
      }

      me._extent = new Extent(xmin, ymin, xmax, ymax)
    }

    return me._extent
  }

  /**
   * 线段读写器
   *
   * @property path
   * @returns
   */
  get path() { return this._path }
  set path(value) {
    this._path = value
    this._extent = null
  }
  // 水平辅助线
  static  getHorizontalRefLineCoords(coordinates, extent){
    let y
    if (coordinates.length !== 2) {
      return false
    }
    y = coordinates[1]
    if (extent) {
      return [[extent[0], y], [extent[2], y]]
    } else {
      return [[-99999, y], [99999, y]]
    }
  }
  
  // 垂直辅助线
  static  getVerticalRefLineCoords(coordinates, extent) {
    let x
    if (coordinates.length !== 2) {
      return false
    }
    x = coordinates[0]
    if (extent) {
      return [[x, extent[1]], [x, extent[3]]]
    } else {
      return [[x, -99999], [x, 99999]]
    }
  }
  /**
   * 添加线段
   *
   * @method addPath
   * @deprecated
   * @param coordinates {Array}
   */
  addPath(coordinates) {
    this.path.push(coordinates)
    this._extent = null
    this.changed()
  }

  /**
   * 多段线的长度
   *
   * @property length
   * @type {Number}
   */
  get length() {
    const coordinates = this.getCoordinates()
    let len = 0
    for (let i = 0, ii = coordinates.length; i < ii - 1; i++) {
      const fp = coordinates[i]
      const np = coordinates[i + 1]
      len += distance(fp, np)
    }

    return len
  }


  changed() {
    super.changed();
    this._extent = null;
  }
  /**
   * 遍历每一条边
   *
   * @method forEachSegment
   * @param callback {callback}
   * @param opt
   */
  forEachSegment(callback, opt) {
    this.path.forEach(callback, opt)
  }

  /**
   * @param x
   * @param y
   * @param opt
   * @returns {boolean}
   */
  containsXY(x, y, opt = {}) {
    const tolerance = opt.tolerance ? opt.tolerance : 2
    const path = this.path
    const squaredSegmentDistanceFn = squaredSegmentDistance

    let find = false
    for (let i = 0, ii = path.length - 1; i < ii; i++) {
      let nowP = path[i]
      let nextP = path[i + 1]
      let distance = squaredSegmentDistanceFn(x, y, nowP[0], nowP[1], nextP[0], nextP[1])
      distance = Math.sqrt(distance)
      if (distance <= tolerance) {
        find = true
        break
      }
    }

    return find
  }

  /**
   * 计算线的平面内点
   *
   * @method getFlatInteriorPoint
   * @returns {Array}
   */
  getFlatInteriorPoint(id) {
    if (this._flatInteriorPointRevision !== this.revision) {
      const interiorPoint = null
      let fraction = 0.5;
      if(id) {
        id = id % 5;
        if(id === 0){
          id = 5
        }
        fraction = id / 5;
      }
      const flatCoordinates = []
      const coordinates = this.getCoordinates()

      coordinates.forEach(point => {
        flatCoordinates.push(point[0], point[1])
      })

      this._flatInteriorPoint = lineString(flatCoordinates, 0, flatCoordinates.length, this.stride,
        fraction, interiorPoint)

      this._flatInteriorPointRevision = this.revision
    }

    return this._flatInteriorPoint
  }

  move(x = 0, y = 0, opts) {
    let beyond
    if (opts && opts.beyond) {
      beyond = opts.beyond
    }

    const oldCoordinates = this.getCoordinates();
    let newCoordinates = [];
    oldCoordinates.forEach(coords => {
      newCoordinates.push([
        coords[0] + x,
        coords[1] + y
      ]);
    });

    let newBezier = [];

    this.bezier.forEach(item => {
      let newPoint = item.points.map(item => {
        return [
          item[0] + x,
          item[1] + y
        ]
      })
      newBezier.push({
        start: item.start,
        points: newPoint
      })
    })

    if (!beyond) {
      this.setCoordinates(newCoordinates);
      this.setBezier(newBezier);
      return;
    }

    let isBeyond = newCoordinates.some(coord => {
      if (
        coord[0] < beyond.xmin ||
        coord[0] > beyond.xmax ||
        coord[1] < beyond.ymin ||
        coord[1] > beyond.ymax
      ) {
        return true;
      }
    });

    if (!isBeyond) {
      this.setCoordinates(newCoordinates);
      this.setBezier(newBezier);
    }
  }

  getCoordinates() { return this.path }

  /**
   * 得到最后一个点的坐标，显示表单
   *
   * @param {Number} offsetX x的偏移量
   * @param {Number} offsetY y的偏移量
   * @returns {[*,*]}
   */
  getFormShowPosition(offsetX = 0, offsetY = 0) {
    const coordinates = this.getCoordinates()
    if (coordinates.length === 0) {
      return null
    }

    const lastPoint = coordinates[coordinates.length - 1]

    return [lastPoint[0] - offsetX, lastPoint[1] - offsetY]
  }

  /**
   * 设置坐标数据
   *
   * @method setCoordinates
   * @param coords {Array}
   */
  setCoordinates(coords) {
    this.path = coords
    this._extent = null
    this.changed()
  }

  /**
   * 根据坐标点，计算改点的下标
   *
   * @method getCoordinateIndex
   * @param coord
   * @returns {*|number}
   */
  getCoordinateIndex(coord) {
    return this.getCoordinates().findIndex(points =>
      points[0] === coord[0] && points[1] === coord[1]
    )
  }

  /**
   * 克隆线段
   *
   * @method clone
   * @returns {Line} new line
   */
  clone() {
    const newCoordinates = [];
    this.getCoordinates().forEach(coords => {
      newCoordinates.push([coords[0], coords[1]])
    })

    const newLine = new Line()
    newLine.setCoordinates(newCoordinates)
    newLine.setBezier(cloneDeep(this.bezier));
    return newLine
  }
  //{ start: 0, //起点 points: [] //控制点}
  addBezier(data) {
    this.bezier.push(data);
    this.changed();
  }

  setBezier(data) {
    this.bezier = data;
    this.changed();
  }
  // data = { start: 0, //起点 points: [] //控制点}
  // return = { start: 0, //起点 points: [] //控制点 renderPoints: []  }
  getBezierItem(data) {
    let coords = this.getCoordinates();
    return {
      start: data.start,
      points: data.points,
      renderPoints: [
        coords[data.start],
        ...data.points,
        coords[data.start + 1]
      ]
    }
  }
  //是否是贝塞尔起点
  //c1: 点或索引
  isBezierStart(c1) {
    if (Array.isArray(c1)) {
      const index = this.getCoordinateIndex(c1);
      return this.bezier.find(item => item.start == index);
    } else {
      return this.bezier.find(item => item.start == c1);
    }
  }

  _getAllControls() {
    let r = [];
    this.bezier.forEach(item => {
      console.log(item, item.points, 'item.points')
      r.push(...item.points);
    })
    return r;
  }

  getBezierVertex(pixelCoordinate, tolerance) {
    let vertex, i;
    let d = this.bezier.find(item => {
      return item.points.some((p, index) => {
        const include = ExtentUtil.containsPoint(
          [
            p[0] - tolerance,
            p[1] - tolerance,
            p[0] + tolerance,
            p[1] + tolerance
          ],
          pixelCoordinate
        )
        if (include) {
          vertex = p;
          i = index;
          return true;
        }
      })
    })
    return { 
      vertex,
      data: d,
      index: i
    }
  }

  updateBezierByStartIndex(index, data) {
    this.bezier.some(item => {
      if (item.start === index) {
        item.points = data;
        this.changed();
        return true;
      }
    })
  }

  removeBezier(data) {
    this.setBezier(
      this.bezier.filter(item => item.start !== data.start)
    )
  }

  hasBezier(index) {
    return this.bezier.find(item => item.start === index);
  }

  readCoordinates(coordinates, types) {
    if (!types || !types.length) {
      this.setCoordinates(coordinates);
    } else {
      let coords = [];
      let bezier = [];
      let LIndex = 0;
      const getNextLIndex = (start, types) => {
        let next;
        for (let i = start, len = types.length; i < len; i++) {
          if (types[i] === 'L') {
            next = i;
            break ;
          }
        }
        return next ? next : -1;
      }

      for (let i = 0, len = coordinates.length; i < len; i++) {
        if (types[i] === 'C') {
          if (types[i - 1] === 'L') {
            let nextIndex = getNextLIndex(i, types);
            bezier.push({
              start: LIndex - 1,
              points: coordinates.slice(i, nextIndex)
            })
          } else {}
        } else {
          coords.push(coordinates[i]);
          LIndex++;
        }
      }

      this.setCoordinates(coords);
      this.setBezier(bezier);
    }
    
  }

  exportCoordinates() {
    let coordinates = this.getCoordinates();
    let bezier = this.bezier;
    const startIndex = {};
    bezier.forEach(item => {
      startIndex[item.start] = item;
    })
    const coords = [];
    const types = [];
    coordinates.forEach((item, index) => {
      coords.push(item);
      types.push('L');
      if (startIndex[index]) {
        startIndex[index].points.forEach(item => {
          coords.push(item);
          types.push('C')
        })
      }
    })
    return { coordinates: coords, types: types.join('') };
  }

  intersectPoint(x, y, tolerance = 3, type = 1) {
    if(type === 1) {
      const point = this._path.find(item => {
        const pointDistance = distance([x,y], item)
        if (pointDistance <= tolerance) {
          return true
        }
      })
      return point;
    }

    if(type === 2) {
      const path = this._path;
      let i = 0;
      let point = null;
      let min = tolerance;

      while (i < path.length - 1) {
        const distance = pointDistanceToSegment([x, y], [path[i], path[i + 1]]);
        if(distance <= tolerance && distance <= min) {
          min = distance;
          point = getProjectionPointOnLine(path[i], path[i + 1], [x, y]);
        }
        i++;
      }

      return point;
    }
  }

}
Line.lineMode = 1 //1曲线 2竖线 //3横线
